/*
 *  gap_buffer.cpp
 *
 *  Author: Hsin Tsao (stsao@lazyhacker.com)
 *  Version: 1.0 (June 12, 2003)
 *
 *  This provides the implementation to the GapBuffer class defined
 *  in text_buffer.h.
 *
 *  Portions of this work derived from Joseph Allen's usenet
 *  postings on comp.editor that was released to the public
 *  domain.
 *
 *
 *  There are no restrictions on the use of this code other
 *  than to include my name in any derived work.  There are
 *  no warranty for this obviously, but you are welcomed
 *  to modify, correct, or adapt the code to your needs.  The
 *  author appreciates if you are willing to submit corrections
 *  or suggestions for improvement.
 *
 *
 *  http://www.lazyhacker.com
 */

#include "gapbuffer.h"
#include <cstdlib>
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>

using namespace std;

GapBuffer::GapBuffer(int gsize) : GAP_SIZE(gsize), buffer(nullptr) { InitBuffer(GAP_SIZE); };

GapBuffer::GapBuffer(FILE *file, int gsize) : GAP_SIZE(gsize) {

	// determine the size of the file then create
	// a buffer of size + GAP_SIZE
	struct stat buf;

	fstat(fileno(file), &buf);
	long file_size = buf.st_size;
	InitBuffer(file_size + GAP_SIZE);
	MoveGapToPoint();
	ExpandGap((int)file_size);
	unsigned int amount = fread(gapstart, 1, file_size, file);

	gapstart += amount;
};

/*  Copy Constructor - Since we have pointers
 *  as member data, we need to provide our own
 *  copy constructor because the default will
 *  only cause copies to all point to the same
 *  location.
 */
GapBuffer::GapBuffer(const GapBuffer &tb) {
	GAP_SIZE = tb.GAP_SIZE;

	buffer = (type::char_type *)malloc((tb.bufend - tb.buffer) *
									   sizeof(type::char_type));

	wcscpy(buffer, tb.buffer);
	bufend = buffer + (tb.bufend - tb.buffer);
	gapstart = buffer + (tb.gapstart - tb.buffer);
	gapend = gapstart + (tb.gapend - tb.gapstart);
	point = buffer + (tb.point - tb.buffer);
}

GapBuffer::~GapBuffer() {

	if (buffer) {
		delete [] buffer;
	}
};

/*
 * Copy the characters from one location to another.  We have
 * to write our own instead of using memcopy because we are
 * working within a single linear buffer and thus can have
 * overlap between the source and destination.
 */
int GapBuffer::CopyBytes(type::char_type *destination, type::char_type *source,
						 unsigned int length) {

	if ((destination == source) || (length == 0)) {
		return 1;
	}

	// if we're moving the character toward the front of the buffer
	if (source > destination) {

		// check to make sure that we don't go beyond the buffer
		if ((source + length) >= bufend) {
			return 0;
		}

		for (; length > 0; length--) {
			*(destination++) = *(source++);
		}
	} else {

		// To prevent overwriting characters we still
		// need to move, go to the back and copy forward.
		source += length;
		destination += length;

		for (; length > 0; length--) {
			// decrement first 'cause we start one byte beyond where we want
			*(--destination) = *(--source);
		}
	}

	return 1;
}

/*
 *  Expand the buffer to new size + GAP_SIZE.
 *
 */
void GapBuffer::ExpandBuffer(unsigned int size) {

	// Check to see that we actually need to increase the buffer
	// since BufferSize doesn't include the gap.
	if (((bufend - buffer) + size) > BufferSize()) {

		type::char_type *origbuffer = buffer;

		int NewBufferSize = (bufend - buffer) + size + GAP_SIZE;

		buffer = (type::char_type *)realloc(
			buffer, NewBufferSize * sizeof(type::char_type));

		point += buffer - origbuffer;
		bufend += buffer - origbuffer;
		gapstart += buffer - origbuffer;
		gapend += buffer - origbuffer;
	}
}

/*
 * Move the gap to the current position of the point.
 * The point should end in same location as gapstart.
 */
void GapBuffer::MoveGapToPoint() {

	if (point == gapstart) {
		return;
	}

	if (point == gapend) {
		point = gapstart;
		return;
	}

	// Move gap towards the left
	if (point < gapstart) {
		// Move the point over by gapsize.
		CopyBytes(point + (gapend - gapstart), point, gapstart - point);
		gapend -= (gapstart - point);
		gapstart = point;
	} else {
		// Since point is after the gap, find distance
		// between gapend and point and that's how
		// much we move from gapend to gapstart.
		CopyBytes(gapstart, gapend, point - gapend);
		gapstart += point - gapend;
		gapend = point;
		point = gapstart;
	}
}

/*
 *  Expand the size of the gap.  If the required
 *  size is less then the current gap size, do
 *  nothing.  If the size is greater than the
 *  current size, increase the gap to the default
 *  gap size + size.
 */
void GapBuffer::ExpandGap(unsigned int size) {

	if (size > SizeOfGap()) {
		size += GAP_SIZE;
		ExpandBuffer(size);
		CopyBytes(gapend + size, gapend, bufend - gapend);

		gapend += size;
		bufend += size;
	}
}

/*
 *  Set point to offset from start of buffer.
 */
void GapBuffer::SetPoint(unsigned int offset) {

	point = buffer + offset;

	if (point > gapstart) {
		point += gapend - gapstart;
	}
}

/*
 *  Returns the current size of the gap.
 */
int GapBuffer::SizeOfGap() { return gapend - gapstart; }

/*
 *  Returns offset from point to start of buffer.
 */
unsigned int GapBuffer::PointOffset() {

	if (point > gapend) {
		return ((point - buffer) - (gapend - gapstart));
	} else {
		return (point - buffer);
	}
}

/*
 * Return character that point is pointing to.
 * If point is inside the gap, then return the
 * the first character outside the gap.
 */
type::char_type GapBuffer::GetChar() {

	// If the point is anywhere in the gap, then
	// it should always be at the start of the gap.
	if (point == gapstart) {
		point = gapend;
	}

	return *point;
}

/*
 * Return the previous character and
 * move point back one position.
 */
type::char_type GapBuffer::PreviousChar() {

	if (point == gapend) {
		point = gapstart;
	}

	return *(--point);
}

/*
 * Replace the character of point.
 */
void GapBuffer::ReplaceChar(type::char_type ch) {

	// Since we're just replacing the current character,
	// we don't need to move or modify the gap.
	if (point == gapstart) {
		point = gapend;
	}

	if (point == bufend) {
		ExpandBuffer(1);
		bufend++;
	}

	*point = ch;
}

/*
 * Increment pointer.  Returns next character.
 */
type::char_type GapBuffer::NextChar() {
	// point should not be in the gap.
	if (point == gapstart) {
		point = gapend;
		return *point;
	}

	return *(++point);
}

void GapBuffer::PutChar(type::char_type ch) {
	InsertChar(ch);
	*point++;
}

/*
 *  Insert character at point position.  Note
 *  that repeatedly calling this function will
 *  keep calling MoveGapToPoint since this function
 *  doesn't advance the point.  The result is the
 *  text appears reverse of key strokes.
 */
void GapBuffer::InsertChar(type::char_type ch) {
	// Here we do need to move the gap if the point
	// is not already at the start of the gap.

	if (point != gapstart) {
		MoveGapToPoint();
	}

	// check to make sure that the gap has room
	if (gapstart == gapend) {
		ExpandGap(1);
	}

	*(gapstart++) = ch;
}

/*
 *  Delete "size" number of characters.
 */
void GapBuffer::DeleteChars(unsigned int size) {

	if (point != gapstart) {
		MoveGapToPoint();
	}

	// We shifted the gap so that gapend points to the location
	// where we want to start deleting so extend it
	// to cover all the characters.
	gapend += size;
}

void GapBuffer::InsertString(type::char_type *string, unsigned int length) {

	MoveGapToPoint();

	if (length > SizeOfGap()) {
		ExpandGap(length);
	}

	do {
		PutChar(*(string++));
	} while (length--);
}

/*
 * Here we initilize the buffer and set
 * the pointers to the correct position.
 */
int GapBuffer::InitBuffer(unsigned int size) {

	if (buffer) {
		delete [] buffer;
	}

	buffer = new type::char_type[size] {0};

	if (!buffer) {
		return 0;
	}

	point = buffer;
	gapstart = buffer;

	// initially gapend is outside of buffer
	gapend = buffer + size;
	bufend = gapend;

	return 1;
}

int GapBuffer::BufferSize() { return (bufend - buffer) - (gapend - gapstart); }

type::char_type *GapBuffer::GetBuffer() const { return buffer; }

void GapBuffer::PrintBuffer() {

	/* type::char_type *temp = buffer; */

	/* while (temp < bufend) { */

	/* 	if ((temp >= gapstart) && (temp < gapend)) { */
	/* 		cout << "_"; */
	/* 		temp++; */
	/* 	} else { */
	/* 		cout << *(temp++); */
	/* 	} */
	/* } */
	/* cout << endl; */
}

int GapBuffer::SaveBufferToFile(FILE *file, unsigned int bytes) {

	if (!bytes) {
		return 1;
	}

	if (point == gapstart) {
		point = gapend;
	}

	if ((gapstart > point) && (gapstart < (point + bytes)) &&
		(gapstart != gapend)) {
		if (gapstart - point != fwrite(point, 1, gapstart - point, file)) {
			return 0;
		}

		if ((bytes - (gapstart - point)) !=
			fwrite(gapend, 1, bytes - (gapstart - point), file)) {
			return 1;
		}

		return 1;
	} else {
		return bytes == fwrite(point, 1, bytes, file);
	}
}
